"""
文件处理工具模块

提供统一的文件处理功能，支持：
1. 从URL下载文件
2. 解析Base64编码的文件流
3. 检测文件类型和格式
"""

import base64
import io
import re
from pathlib import Path
from typing import BinaryIO, Optional, Tuple, Union
from urllib.parse import urlparse

import httpx
from PIL import Image

from src.utils.exceptions import FileProcessingError


class FileHandler:
    """文件处理器，提供统一的文件输入处理功能"""
    
    # 支持的图像格式
    SUPPORTED_IMAGE_FORMATS = {'.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif', '.webp', '.gif'}
    
    # 支持的文档格式（可直接解析）
    SUPPORTED_DOCUMENT_FORMATS = {'.pdf'}
    
    # 支持的 Excel 格式
    SUPPORTED_EXCEL_FORMATS = {'.xlsx', '.xlsm'}
    
    # 支持的 CSV 格式
    SUPPORTED_CSV_FORMATS = {'.csv'}
    
    # 支持的 Word 文档格式（通过 Tika 提取文本，通过 Gotenberg 转换用于溯源）
    SUPPORTED_WORD_FORMATS = {
        '.doc', '.docx', '.dot', '.dotx'   # Word
    }
    
    # 支持的文本文档格式（仅通过 Tika 提取，不支持溯源）
    SUPPORTED_TEXT_DOCUMENT_FORMATS = {
        '.txt', '.text',                    # 纯文本
        '.rtf',                             # Rich Text
        '.odt', '.ott',                     # OpenDocument
        '.html', '.htm',                    # HTML
        '.md', '.markdown',                 # Markdown
        '.xlsx', '.xlsm',                   # Excel（通过 Tika 提取文本）
        '.csv'                              # CSV（通过 Tika 提取文本）
    }
    
    @classmethod
    async def process_file_input(
        cls, 
        file_url: Optional[str] = None, 
        file_stream: Optional[str] = None,
        file_name: str = ""
    ) -> Tuple[bytes, str]:
        """
        处理文件输入，统一返回文件内容和文件类型
        
        Args:
            file_url: 文件URL地址
            file_stream: 文件流（Base64编码或Data URI）
            file_name: 文件名称
            
        Returns:
            Tuple[bytes, str]: (文件内容, 文件扩展名)
            
        Raises:
            FileProcessingError: 文件处理失败时抛出
        """
        if file_url:
            return await cls._download_from_url(file_url)
        elif file_stream:
            return cls._parse_file_stream(file_stream, file_name)
        else:
            raise FileProcessingError("必须提供 file_url 或 file_stream 中的一个")
    
    @classmethod
    async def _download_from_url(cls, url: str) -> Tuple[bytes, str]:
        """
        从URL下载文件
        
        Args:
            url: 文件URL
            
        Returns:
            Tuple[bytes, str]: (文件内容, 文件扩展名)
        """
        try:
            parsed_url = urlparse(url)
            file_extension = Path(parsed_url.path).suffix.lower()
            
            async with httpx.AsyncClient() as client:
                response = await client.get(url, timeout=30.0)
                response.raise_for_status()
                
                # 验证Content-Type（可选）
                content_type = response.headers.get('content-type', '').lower()
                if not cls._is_supported_content_type(content_type, file_extension):
                    # 仍然尝试处理，因为有些服务器可能返回错误的Content-Type
                    pass
                    
                return response.content, file_extension
                
        except httpx.RequestError as e:
            raise FileProcessingError(f"下载文件失败: {str(e)}")
        except httpx.HTTPStatusError as e:
            raise FileProcessingError(f"HTTP错误 {e.response.status_code}: {e.response.text}")
    
    @classmethod
    def _parse_file_stream(cls, file_stream: str, file_name: str) -> Tuple[bytes, str]:
        """
        解析文件流（Base64或Data URI）
        
        Args:
            file_stream: 文件流数据
            file_name: 文件名称
            
        Returns:
            Tuple[bytes, str]: (文件内容, 文件扩展名)
        """
        try:
            # 获取文件扩展名
            file_extension = Path(file_name).suffix.lower() if file_name else ''
            
            # 检查是否为Data URI格式
            if file_stream.startswith('data:'):
                # 解析Data URI: data:mime_type;base64,data
                match = re.match(r'data:([^;]+);base64,(.+)', file_stream)
                if not match:
                    raise FileProcessingError("无效的Data URI格式")
                    
                mime_type, base64_data = match.groups()
                
                # 如果没有扩展名，尝试从MIME类型推导
                if not file_extension:
                    file_extension = cls._mime_type_to_extension(mime_type)
                    
                file_content = base64.b64decode(base64_data)
            else:
                # 纯Base64编码
                file_content = base64.b64decode(file_stream)
                
            # 验证文件格式
            if not cls.is_supported_format(file_extension):
                raise FileProcessingError(f"不支持的文件格式: {file_extension}")
                
            return file_content, file_extension
            
        except base64.binascii.Error:
            raise FileProcessingError("Base64解码失败，数据可能损坏")
        except Exception as e:
            raise FileProcessingError(f"解析文件流失败: {str(e)}")
    
    @classmethod
    def _mime_type_to_extension(cls, mime_type: str) -> str:
        """根据MIME类型推导文件扩展名"""
        mime_to_ext = {
            'image/jpeg': '.jpg',
            'image/png': '.png',
            'image/bmp': '.bmp',
            'image/tiff': '.tiff',
            'image/webp': '.webp',
            'image/gif': '.gif',
            'application/pdf': '.pdf',
            'application/msword': '.doc',
            'application/vnd.openxmlformats-officedocument.wordprocessingml.document': '.docx',
            'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': '.xlsx',
            'application/vnd.ms-excel.sheet.macroEnabled.12': '.xlsm',
            'text/csv': '.csv',
            'application/csv': '.csv',
            'text/plain': '.txt',
            'text/rtf': '.rtf',
            'application/rtf': '.rtf',
            'text/html': '.html',
            'text/markdown': '.md',
        }
        return mime_to_ext.get(mime_type.lower(), '')
    
    @classmethod
    def _is_supported_content_type(cls, content_type: str, file_extension: str) -> bool:
        """检查Content-Type是否支持"""
        supported_types = {
            'image/jpeg', 'image/jpg', 'image/png', 'image/bmp', 
            'image/tiff', 'image/webp', 'image/gif', 'application/pdf',
            'application/msword', 'application/vnd.openxmlformats-officedocument',
            'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            'application/vnd.ms-excel.sheet.macroEnabled.12',
            'text/csv', 'application/csv',
            'text/plain', 'text/rtf', 'application/rtf', 'text/html', 'text/markdown'
        }
        return any(ct in content_type for ct in supported_types)
    
    @classmethod
    def is_supported_format(cls, file_extension: str) -> bool:
        """检查文件格式是否支持"""
        return (
            file_extension in cls.SUPPORTED_IMAGE_FORMATS or 
            file_extension in cls.SUPPORTED_DOCUMENT_FORMATS or
            file_extension in cls.SUPPORTED_EXCEL_FORMATS or
            file_extension in cls.SUPPORTED_CSV_FORMATS or
            file_extension in cls.SUPPORTED_WORD_FORMATS or
            file_extension in cls.SUPPORTED_TEXT_DOCUMENT_FORMATS
        )
    
    @classmethod
    def is_image_format(cls, file_extension: str) -> bool:
        """检查是否为图像格式"""
        return file_extension in cls.SUPPORTED_IMAGE_FORMATS
    
    @classmethod
    def is_pdf_format(cls, file_extension: str) -> bool:
        """检查是否为PDF格式"""
        return file_extension == '.pdf'
    
    @classmethod
    def is_excel_format(cls, file_extension: str) -> bool:
        """检查是否为Excel格式"""
        return file_extension in cls.SUPPORTED_EXCEL_FORMATS
    
    @classmethod
    def is_csv_format(cls, file_extension: str) -> bool:
        """检查是否为CSV格式"""
        return file_extension in cls.SUPPORTED_CSV_FORMATS
    
    @classmethod
    def is_word_format(cls, file_extension: str) -> bool:
        """检查是否为 Word 格式（支持溯源）"""
        return file_extension in cls.SUPPORTED_WORD_FORMATS
    
    @classmethod
    def is_text_document_format(cls, file_extension: str) -> bool:
        """检查是否为文本文档格式（仅提取，不支持溯源）"""
        return file_extension in cls.SUPPORTED_TEXT_DOCUMENT_FORMATS
    
    @classmethod
    def needs_conversion_to_pdf(cls, file_extension: str) -> bool:
        """检查是否需要转换为 PDF（图片格式）"""
        return file_extension in cls.SUPPORTED_IMAGE_FORMATS and file_extension != '.pdf'
    
    @classmethod
    def create_temp_file(cls, content: bytes, extension: str) -> io.BytesIO:
        """
        创建临时文件对象
        
        Args:
            content: 文件内容
            extension: 文件扩展名
            
        Returns:
            io.BytesIO: 文件对象
        """
        file_obj = io.BytesIO(content)
        file_obj.seek(0)
        return file_obj
    
    @classmethod
    def validate_image_file(cls, content: bytes) -> bool:
        """
        验证图像文件是否有效
        
        Args:
            content: 图像文件内容
            
        Returns:
            bool: 是否为有效图像
        """
        try:
            with Image.open(io.BytesIO(content)) as img:
                img.verify()  # 验证图像完整性
                return True
        except Exception:
            return False
    
    @classmethod
    def validate_excel_file(cls, content: bytes) -> bool:
        """
        验证Excel文件是否可以正常读取（检查是否加密）
        
        Args:
            content: Excel文件内容
            
        Returns:
            bool: 是否可以正常读取（未加密）
        """
        try:
            from openpyxl import load_workbook
            
            # 尝试加载工作簿，如果文件被加密或损坏会抛出异常
            workbook = load_workbook(io.BytesIO(content), read_only=True, data_only=True)
            # 尝试访问第一个工作表以确认可以读取
            if len(workbook.sheetnames) > 0:
                sheet = workbook[workbook.sheetnames[0]]
                # 尝试读取一些基本信息
                _ = sheet.max_row
                _ = sheet.max_column
            workbook.close()
            return True
        except Exception:
            return False
    
    @classmethod
    def validate_csv_file(cls, content: bytes) -> bool:
        """
        验证CSV文件是否可以正常读取
        
        Args:
            content: CSV文件内容
            
        Returns:
            bool: 是否可以正常读取
        """
        try:
            import csv
            
            # 尝试解析CSV文件，如果文件损坏或格式错误会抛出异常
            content_str = content.decode('utf-8-sig')  # 使用utf-8-sig处理BOM
            csv_reader = csv.reader(io.StringIO(content_str))
            
            # 尝试读取至少一行数据以确认可以解析
            try:
                next(csv_reader)
            except StopIteration:
                # 空文件也算可以读取
                pass
            
            return True
        except UnicodeDecodeError:
            # 尝试其他编码
            try:
                content_str = content.decode('gbk')
                csv_reader = csv.reader(io.StringIO(content_str))
                try:
                    next(csv_reader)
                except StopIteration:
                    pass
                return True
            except Exception:
                return False
        except Exception:
            return False
